﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualStudio.Data.Schema.Package.Project;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Editors.PropertyPages;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Data.Schema.Package.UI;
using System.Reflection;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using TFDP.Common;
using TFDP.Common.Configuration;
using System.Xml.Serialization;
using System.IO;
using System.Xml;
using TFDP.VsPackage.Services;
using System.ComponentModel.Design;

namespace TFDP.VsPackage.Forms
{
    [ProvideObject(typeof(TFDPPropertyPageComClass), RegisterUsing = RegistrationMethod.CodeBase)]
    public partial class TFDPPropertyPage : DatabasePropertyPageUserControlBase, IOleCommandTarget
    {
        internal static Guid _guidStandardCommandSet97;

        #region Members
        private uint _priorityCommandTargetCookie;
        private bool pageIsDirty;
        private bool initializing;
        private ProcessorConfigurationWrapper wrapper;
        #endregion

        #region Properties
        [Import(typeof(IProcessor))]
        internal IProcessor Processor { get; set; }
        #endregion

        #region Construction and Initialization
        static TFDPPropertyPage()
        {
            _guidStandardCommandSet97 = new Guid("5efc7975-14bc-11cf-9b2b-00aa00573819");
        }

        public TFDPPropertyPage()
            : base()
        {
            this.InitializeComponent();
            this.InitializeBindings();

            base.AddChangeHandlers();
        }

        private void InitializeBindings()
        {
            groupBoxProcessor.DataBindings.Add("Enabled", checkBoxEnableGeneration, "Checked");
        }

        private void InitializePage()
        {
            initializing = true;

            // EnableGeneration
            string propertyValue = base.DatabaseProjectNode.GetProp("TFDP_EnableGeneration");
            checkBoxEnableGeneration.Checked = (!String.IsNullOrEmpty(propertyValue) && propertyValue == Boolean.TrueString);

            // TemplateDirectory
            propertyValue = base.DatabaseProjectNode.GetProp("TFDP_TemplateDirectory");
            templateDirectory.Text = propertyValue;

            // ProcessorName
            propertyValue = base.DatabaseProjectNode.GetProp("TFDP_ProcessorName");
            processorName.Text = propertyValue;
            LoadProcessor();
             
            // Settings
            if (wrapper != null)
                wrapper.Settings = base.DatabaseProjectNode.GetProcessorSettings();

            // Register save command
            if (this._priorityCommandTargetCookie == 0)
            {
                IVsRegisterPriorityCommandTarget service = DataPackage.Instance.GetService<SVsRegisterPriorityCommandTarget, IVsRegisterPriorityCommandTarget>();
                if (service != null)
                {
                    service.RegisterPriorityCommandTarget(0, this, out this._priorityCommandTargetCookie);
                }
            }

            initializing = false;
        } 
        #endregion

        #region Property Page Overrides
        protected override Microsoft.VisualStudio.Editors.PropertyPages.PropertyControlData[] ControlData
        {
            get
            {
                if (base.m_ControlData == null)
                {
                    return new PropertyControlData[0];
                }
                return base.m_ControlData;
            }
        }

        public override void SetObjects(object[] objects)
        {
            base.SetObjects(objects);

            this.propertyGridProcessor.SelectedObject = base.DatabaseProjectNode.NodeProperties;

            if (!base.DatabaseProjectNode.IsBuildInProgress && !base.DatabaseProjectNode.ModelBuildInProgress)
            {
                this.InitializePage();
            }
        }

        protected override void OnBuildBegin(EnvDTE.vsBuildScope scope, EnvDTE.vsBuildAction action)
        {
            this.Apply();

            base.OnBuildBegin(scope, action);
        }

        protected override void OnBuildEnd(EnvDTE.vsBuildScope scope, EnvDTE.vsBuildAction action)
        {
            this.InitializePage();
        }

        public override void Apply()
        {
            base.Apply();

            this.pageIsDirty = false;
        }

        protected override bool IsPageDirty()
        {
            return pageIsDirty;
        }
        #endregion

        #region IOleCommandTarget Members

        public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            int num = -2147221248;
            if (pguidCmdGroup == _guidStandardCommandSet97)
            {
                VSConstants.VSStd97CmdID did = (VSConstants.VSStd97CmdID)nCmdID;
                if ((did != VSConstants.VSStd97CmdID.SaveSolution) && (did != VSConstants.VSStd97CmdID.SaveProjectItem))
                {
                    return num;
                }
                this.Apply();
            }
            return num;
        }

        public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
            return -2147221248;
        }

        #endregion

        #region UserControl events
        private void checkBoxEnableGeneration_CheckedChanged(object sender, EventArgs e)
        {
            if (initializing)
                return;

            base.DatabaseProjectNode.SetProjectProperty("TFDP_EnableGeneration", checkBoxEnableGeneration.Checked.ToString(), DatabaseProjectEventPropagation.RaiseEvent);
        }

        private void processorName_TextChanged(object sender, EventArgs e)
        {
            if (initializing)
                return;

            base.DatabaseProjectNode.SetProjectProperty("TFDP_ProcessorName", processorName.Text.Trim(), DatabaseProjectEventPropagation.RaiseEvent);

            LoadProcessor();
        }

        private void propertyGridProcessor_PropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
        {
            if (initializing)
                return;

            base.DatabaseProjectNode.SetProjectProperty("TFDP_Settings", SettingsCollection.SerializeSettings(wrapper.Settings), DatabaseProjectEventPropagation.RaiseEvent);
        }

        private void templateDirectory_TextChanged(object sender, EventArgs e)
        {
            if (initializing)
                return;

            if (wrapper != null)
            {
                base.DatabaseProjectNode.SetProjectProperty("TFDP_TemplateDirectory", templateDirectory.Text.Trim(), DatabaseProjectEventPropagation.RaiseEvent);
            }
        }
        #endregion

        #region Private Methods
        private void LoadProcessor()
        {
            string name = processorName.Text.Trim();

            if (String.IsNullOrEmpty(name))
                return;

            Assembly assembly = null;
            try
            {
                assembly = Assembly.Load(name);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to load processor: " + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);

                // Disable the processor settings
                groupBoxProcessor.Enabled = false;

                return;
            }

            // Enable the processor settings
            groupBoxProcessor.Enabled = checkBoxEnableGeneration.Enabled && true;

            var catalog = new AssemblyCatalog(assembly);
            var container = new CompositionContainer(catalog);

            container.ComposeParts(this);

            this.wrapper = new ProcessorConfigurationWrapper(this.Processor);
            propertyGridProcessor.SelectedObject = wrapper;
            listBoxSupportedTypes.DataSource = wrapper.SupportedTypes;
            listBoxGenerators.DataSource = wrapper.Generators;
        }

        private void buttonBrowse_Click(object sender, EventArgs e)
        {
            browseProcessorDialog.InitialDirectory = Environment.CurrentDirectory;

            DialogResult result = browseProcessorDialog.ShowDialog(this);

            if (result == DialogResult.OK)
            {
                string path = browseProcessorDialog.FileName;

                Assembly asm = Assembly.ReflectionOnlyLoadFrom(path);

                processorName.Text = asm.FullName;
            }
        }
        #endregion
    }

    [TypeDescriptionProvider(typeof(SettingsDescriptorProvider))]
    internal class ProcessorConfigurationWrapper : INotifyPropertyChanged
    {
        private IProcessor processor;

        public ProcessorConfigurationWrapper(IProcessor processor)
        {
            this.processor = processor;
        }

        [Description("The types of objects supported by this processor")]
        [Browsable(true)]
        [ReadOnly(true)]
        public Type[] SupportedTypes
        {
            get
            {
                return processor.SupportedTypes.ToArray<Type>();
            }
        }

        [Description("The properties that are provided on the object types")]
        [Browsable(true)]
        [ReadOnly(true)]
        public SettingsCollection Settings
        {
            get
            {
                return processor.Settings;
            }
            set
            {
                foreach (ISetting setting in value)
                {
                    processor.Settings[setting.Key].Value = setting.Value;

                    var match = from s in processor.Settings
                                where s.Key == setting.Key
                                select s;

                    if (match.Count() == 1)
                        match.First().Value = setting.Value;

                    RaisePropertyChanged("Settings");
                }
            }
        }

        [Description("The generators that exist in this processor")]
        [Browsable(true)]
        [ReadOnly(true)]
        public IGenerator[] Generators
        {
            get
            {
                dynamic d = this.processor;
                
                return d.Generators;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }

    internal class SettingsDescriptorProvider : TypeDescriptionProvider
    {
        public SettingsDescriptorProvider() : this(null)
        {
        }

        public SettingsDescriptorProvider(TypeDescriptionProvider parent)
            : base(parent)
        {
        }

        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
        {
            if (objectType == typeof(ProcessorConfigurationWrapper))
            {
                SettingsDescriptor descriptor = new SettingsDescriptor();

                if(instance != null)
                    descriptor.Settings = ((ProcessorConfigurationWrapper)instance).Settings;

                return descriptor;
            }
            else
                return base.GetTypeDescriptor(objectType, instance);
        }
    }

    internal class SettingsDescriptor : CustomTypeDescriptor
    {
        private SettingsCollection settings;

        public SettingsCollection Settings
        {
            get { return settings; }
            set { settings = value; }
        }

        #region Constructors
        public SettingsDescriptor()
            : base()
        {
        }

        public SettingsDescriptor(ICustomTypeDescriptor parent)
            : base(parent)
        {
        } 
        #endregion

        public override PropertyDescriptorCollection GetProperties()
        {
            return GetProperties(null);
        }

        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            List<PropertyDescriptor> additionalProperties = new List<PropertyDescriptor>();

            if(this.settings != null)
            {
                foreach (ISetting setting in settings)
                    additionalProperties.Add(new SettingPropertyDescriptor(setting));
            }

            return new PropertyDescriptorCollection(additionalProperties.ToArray(), true);
        }

        public override object GetPropertyOwner(PropertyDescriptor pd)
        {
            var match = settings
                .Where(s => s.Key == pd.Name)
                .Select(s => s);

            if (match.Count() == 0)
                return null;
            else
                return match.First();
        }
    }

    internal class SettingPropertyDescriptor : PropertyDescriptor
    {
        private ISetting setting;
        private object originalValue;

        public SettingPropertyDescriptor(ISetting setting)
            : base(setting.Key, null)
        {
            this.setting = setting;
            this.originalValue = setting.Value;
        }

        public override object GetEditor(Type editorBaseType)
        {
            Type type = this.setting.ValueType;
            
            var attributes = type.GetCustomAttributes(true);

            var editors = attributes.Where(a => a.GetType() == typeof(EditorAttribute));
            if (editors.Count() == 1)
            {
                var editorAttribute = editors.First() as EditorAttribute;
                Type editorType = Type.GetType(editorAttribute.EditorTypeName);

                return Activator.CreateInstance(editorType);
            }
            else
                return null;
        }

        public override object GetValue(object component)
        {
            return setting.Value;
        }

        public override string Name
        {
            get
            {
                return setting.Key;
            }
        }

        public override string Description
        {
            get
            {
                return setting.Key;
            }
        }

        public override bool ShouldSerializeValue(object component)
        {
            return true;
        }

        public override void SetValue(object component, object value)
        {
            setting.Value = value;
        }

        public override void ResetValue(object component)
        {
            setting.Value = originalValue;
        }

        public override bool CanResetValue(object component)
        {
            return true;
        }

        public override Type PropertyType
        {
            get { return typeof(string); }
        }

        public override bool IsReadOnly
        {
            get { return false; }
        }

        public override Type ComponentType
        {
            get { return typeof(ISetting); }
        }

        public override string Category
        {
            get
            {
                return "Processor settings";
            }
        }

        public override string DisplayName
        {
            get
            {
                string name = this.Name;
                StringBuilder builder = new StringBuilder();

                bool first = true;
                foreach (char c in name)
                {
                    if (Char.IsUpper(c) && !first)
                        builder.Append(" ");

                    first = false;

                    builder.Append(c);
                }

                return builder.ToString();
            }
        }
    }
}
